#include <linux/module.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h> 
#include <linux/types.h> 
#include <linux/spinlock.h>
#include <linux/kfifo.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

static int kfifo_major = 0;
static spinlock_t fifo_lock;
static struct kfifo * fifo;

#define BUFFER_SIZE 4096
static DECLARE_WAIT_QUEUE_HEAD(fifo_list);
static char * rbuf, * wbuf;

static ssize_t kfifo_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
	int ret = 0;

	if(count > BUFFER_SIZE)
		count = BUFFER_SIZE;

	wait_event_interruptible(fifo_list, kfifo_len(fifo) != 0);

	ret = kfifo_get(fifo, rbuf, count);
	if(copy_to_user(buf, rbuf, ret))
                return -EFAULT;

	return ret;
}

static ssize_t kfifo_write (struct file *filp, const char __user *buf, size_t count,
		loff_t *pos)
{
	int ret = 0;

	if(count > BUFFER_SIZE)
		count = BUFFER_SIZE;

	if(copy_from_user(wbuf, buf, count))
                return -EFAULT;

	ret = kfifo_put(fifo, wbuf, count);

	wake_up_interruptible(&fifo_list);

	return ret;
}

static struct file_operations kfifo_fops = {
	.owner = THIS_MODULE,
	.read =  kfifo_read,
	.write = kfifo_write,
};

static int __init agui_kfifo_init(void)
{
	int result;

	result = register_chrdev(kfifo_major, "kfifo", &kfifo_fops);
	if (result < 0)
		return result;
	if (kfifo_major == 0)
		kfifo_major = result; /* dynamic */

	spin_lock_init(&fifo_lock);
	fifo = kfifo_alloc(BUFFER_SIZE, GFP_KERNEL, &fifo_lock);

	wbuf = (char *)kmalloc(BUFFER_SIZE, GFP_KERNEL);
	if(wbuf == NULL)
		return -ENOMEM;
	rbuf = (char *)kmalloc(BUFFER_SIZE, GFP_KERNEL);
	if(rbuf == NULL)
		return -ENOMEM;

	return 0;
}

static void __exit agui_kfifo_cleanup(void)
{
	kfifo_free(fifo);
	if(wbuf != NULL) 
		kfree(wbuf);
	if(rbuf != NULL)
		kfree(rbuf);
	unregister_chrdev(kfifo_major, "kfifo");
}

module_init(agui_kfifo_init);
module_exit(agui_kfifo_cleanup);
